﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;

using PotatoSoft.VOCTS.ScriptInterface;

namespace PotatoSoft
{
    namespace VOCTS
    {
        namespace ScriptEngine
        {
            /// <summary>
            /// Class with engine to running scripts. 
            /// </summary>
            public class ScriptEngine
            {
                // fields and properties to sets application interfaces for scripting
                #region vocts interfaces

                private I3dRepresentation represntation3D;
                private IDiscOperation discOperation;

                /// <summary>
                /// Get or set implementation of I3dRepresentation interface for scripting.
                /// </summary>
                public I3dRepresentation Representation3D
                {
                    set { represntation3D = value; }
                    get { return represntation3D; }
                }

                /// <summary>
                /// Get or set implementation of IDiscOpeartion interface for scripting.
                /// </summary>
                public IDiscOperation DiscOperation
                {
                    set { discOperation = value; }
                    get { return discOperation; }
                }

                #endregion

                List<string> errors = new List<string>();

                /// <summary>
                /// Get message about errors occurring during compilation or execution of scripts. 
                /// </summary>
                public string[] GetLastErrors
                {
                    get { return errors.ToArray(); }
                }

                /// <summary>
                /// Method to compile and execute script from file.
                /// </summary>
                /// <param name="filename">Path to file with code to execution.</param>
                /// <returns>True if script execution ends with success, false if there were errors (to check errors read ErrorMessage property).</returns>
                public bool RunScript(FileInfo fi)
                {
                    errors.Clear();
                    if (fi.Exists)
                    {
                        StreamReader sr = new StreamReader(fi.FullName);
                        // read file text to string
                        string code = sr.ReadToEnd();
                        // exetuce script
                        return this.RunScript(code);
                    }
                    // there is no file 
                    errors.Add("File "+fi.FullName+" doesn't exists");
                    return false;
                }

                /// <summary>
                /// Method to compile and execute script from string array.
                /// </summary>
                /// <param name="scirptCode">String array with code to compile.</param>
                /// <returns>True if script execution ends with success, false if there were errors (to check errors read GetLastErrors property).</returns>
                public bool RunScript(string scirptCode)
                {
                    errors.Clear();
                    // creating provider
                    CodeDomProvider csProvider = CodeDomProvider.CreateProvider("CSharp");

                    // creating paramters for execution
                    CompilerParameters csPrarams = new CompilerParameters();
                    csPrarams.GenerateExecutable = false;
                    csPrarams.GenerateInMemory = true;
                    csPrarams.CompilerOptions = "/optimize";
                    csPrarams.IncludeDebugInformation = false;
                    // adding reference to VOCTS interfaces
                    csPrarams.ReferencedAssemblies.Add("PotatoSoft.ScriptInterface.dll");

                    // add additional source code to script 
                    scirptCode.Insert(0, "using System;\nusing PotatoSoft.VOCTS.ScriptInterface");

                    // compilation
                    CompilerResults csResult = csProvider.CompileAssemblyFromSource(csPrarams, scirptCode);
                    if (csResult.Errors.Count > 0)
                    {
                        // there were compilation errors
                        foreach (CompilerError cErr in csResult.Errors)
                            errors.Add("Compilaton error, line: " + cErr.Line + " message: " + cErr.ErrorText);
                        return false;
                    }

                    // created assembly
                    Assembly compilationResult = csResult.CompiledAssembly;
                    return this.RunAssembly(compilationResult);
                }

                /// <summary>
                /// Method to run compiled script.
                /// </summary>
                /// <param name="compilationResult">Result of script compilation.</param>
                /// <returns>True if script execution ends with success, false if there were errors (to check errors read GetLastErrors property).</returns>
                private bool RunAssembly(Assembly compilationResult)
                {
                    // adding paramteres to script, this parameters should be declared as main method parameters
                    object[] parametes = { this.DiscOperation, this.Representation3D };
                    // flag to chech if there was main method in assembly types
                    bool mainMethodFlag = false;
                    // all classes from aseembly
                    Type[] types = compilationResult.GetTypes();
                    // searching static main method in all classes
                    foreach (Type t in types)
                    {
                        MethodInfo methodInfo = t.GetMethod("Main", BindingFlags.Public | BindingFlags.Static);
                        if (methodInfo != null)
                        {
                            // there is main method
                            try
                            {
                                mainMethodFlag = true;
                                // try to run
                                methodInfo.Invoke(null, parametes);
                                break;
                            }
                            catch (System.Exception ex)
                            {
                                errors.Add("Runtime error, message: " + ex.Message);
                                return false;
                            }
                        }
                    }
                    if (!mainMethodFlag)
                    {
                        errors.Add("No main method in assembly");
                        return false;
                    }
                    // success
                    return true;
                }

                /// <summary>
                /// Method to add standard C# code to script.
                /// </summary>
                /// <param name="scriptCode">Script code from user</param>
                private void AddSourceCode(string scriptCode)
                {
                    scriptCode.Insert(0, "using System;\nusing PotatoSoft.VOCTS.ScriptInterface");
                }
            }
        }
    }
}
